﻿using System;
using System.Collections.Generic;
using System.Threading;
using Loki.Utils;

namespace Loki.Model
{
    public class IndexedKeyMapper<TEntityClass, TEntityIndex> : LokiObject, IDisposable, IKeyMapper<TEntityClass>
    {
        #region Disposable
        /// <inheritdoc />
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <inheritdoc />
        ~IndexedKeyMapper()
        {
            Dispose(false);
        }

        /// <inheritdoc />
        protected virtual void Dispose(bool P_Disposing)
        {
            if (P_Disposing)
            {
                _Lock.Dispose();
                _IndexStore.Clear();
                _FullLoaded.Clear();
            }
        }
        #endregion

        public IndexedKeyMapper(IIndexConfiguration<TEntityClass> P_Configuration, IEqualityComparer<TEntityClass> P_ClassComparer = null)
        {
            // check config
            IndexConfiguration<TEntityClass, TEntityIndex> L_Config = P_Configuration as IndexConfiguration<TEntityClass, TEntityIndex>;
            if (L_Config == null)
            {
                // throw BuildErrorFormat<ArgumentException>(ToolsErrors.TOO_003, P_Configuration.GetType(), GetType());
            }

            // init store
            _IndexStore = new Dictionary<TEntityIndex, HashSet<TEntityClass>>(L_Config.IndexComparer);
            _FullLoaded = new HashSet<TEntityIndex>(L_Config.IndexComparer);

            // lock
            _Lock = new ReaderWriterLockSlim();

            // indexer
            Func<TEntityClass, object> L_BulkIndexer = L_Config.Indexer.Compile();
            _Indexer = x => (TEntityIndex)L_BulkIndexer(x);

            // class identifier
            _ClassComparer = P_ClassComparer;
        }

        private ReaderWriterLockSlim _Lock;

        private Dictionary<TEntityIndex, HashSet<TEntityClass>> _IndexStore;

        private HashSet<TEntityIndex> _FullLoaded;

        private Func<TEntityClass, TEntityIndex> _Indexer;

        private IEqualityComparer<TEntityClass> _ClassComparer;

        public IEnumerable<TEntityClass> GetAllByKey(object P_Key)
        {
            return _Lock.WithRead(() => _GetAllByKey((TEntityIndex)P_Key));
        }

        private IEnumerable<TEntityClass> _GetAllByKey(TEntityIndex P_Key)
        {
            if (P_Key != null && _IndexStore.ContainsKey(P_Key))
            {
                return _IndexStore[P_Key];
            }
            else
            {
                return new TEntityClass[0];
            }
        }

        public void Reset()
        {
            _Lock.WithWrite(_Reset);
        }

        private void _Reset()
        {
            _FullLoaded.Clear();
            _IndexStore.Clear();
        }

        public bool Register(TEntityClass P_Entity)
        {
            return _Lock.WithWrite(() => _Register(P_Entity));
        }

        private bool _Register(TEntityClass P_Entity)
        {
            TEntityIndex L_EntityHash = _Indexer(P_Entity);

            if (L_EntityHash != null)
            {
                if (!_IndexStore.ContainsKey(L_EntityHash))
                {
                    _IndexStore.Add(L_EntityHash, new HashSet<TEntityClass>(_ClassComparer));
                }

                return _IndexStore[L_EntityHash].Add(P_Entity);
            }

            return false;
        }

        public void Remove(TEntityClass P_Entity)
        {
            _Lock.WithWrite(() => _Remove(P_Entity));
        }

        private void _Remove(TEntityClass P_Entity)
        {
            TEntityIndex L_EntityHash = _Indexer(P_Entity);

            if (L_EntityHash != null)
            {
                // registered hash
                if (_IndexStore.ContainsKey(L_EntityHash) && _IndexStore[L_EntityHash].Contains(P_Entity))
                {
                    // remove registered entity
                    _IndexStore[L_EntityHash].Remove(P_Entity);
                }
            }
        }

        public bool IsLoaded(object P_Key)
        {
            return _Lock.WithWrite(() => _IsLoaded((TEntityIndex)P_Key));
        }

        private bool _IsLoaded(TEntityIndex P_Index)
        {
            return _FullLoaded.Contains(P_Index);
        }

        public void MarkAsLoaded(object P_Key)
        {
            _Lock.WithWrite(() => _MarkAsLoaded((TEntityIndex)P_Key));
        }

        private void _MarkAsLoaded(TEntityIndex P_Key)
        {
            _FullLoaded.Add(P_Key);
        }

        public object ComputeIndex(TEntityClass P_Entity)
        {
            return _Indexer(P_Entity);
        }
    }
}
